package main

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"fmt"
	"github.com/Masterminds/squirrel"
	_ "github.com/taosdata/driver-go/v3/taosRestful"
	"github.com/zeromicro/go-zero/core/jsonx"
	"log"
	"reflect"
	"time"
)

type AppLog struct {
	Ts          time.Time `json:"ts"`
	CreatedName string    `json:"created_name"`
}

type TdDb struct {
	DbName    string // 子数据库名称
	TableName string // 超级表名称
}

func main() {

	// 连接Td
	var taosUri = "root:POOTACA93V@http(127.0.0.1:6041)/"
	taos, err := sql.Open("taosRestful", taosUri)
	if err != nil {
		fmt.Println("failed to connect TDengine, err:", err)
		return
	}
	//createDataBase(taos)

	tdDb := &TdDb{
		DbName:    "app_log.d1",           // 子数据库名称
		TableName: "app_log.tpmt_app_log", // 超级表名称
	}

	var data = AppLog{
		Ts:          time.Now(),
		CreatedName: "",
	}

	ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)

	//err = data.Insert(ctx, taos, tdDb)
	//
	res, err := data.FindList(ctx, taos, tdDb, 1, 10, 0, 0)

	if err != nil {
		fmt.Println(data)

	}

	count := data.Count(ctx, taos, tdDb, 0, 0)

	if err != nil {
		fmt.Println(data)

	}

	fmt.Println(count)
	fmt.Println(res)

	defer taos.Close()

}

func (t AppLog) Insert(ctx context.Context, taos *sql.DB, tddb *TdDb) error {

	// 拼接请求数据库和表
	dbName := fmt.Sprintf("INSERT INTO %s USING %s ", tddb.DbName, tddb.TableName)

	// 拼接参数
	tableData := fmt.Sprintf(" Tags('%v') (`ts`,`created_name`)", "2")

	value := fmt.Sprintf(" values ('%v','%v');", t.Ts.Format(time.RFC3339Nano), t.CreatedName)
	//value := fmt.Sprintf(" values ('%v','%v');", "2024-09-18T15:13:46+08:00", t.CreatedName)

	sqlx := dbName + tableData + value

	// 创建30秒协程写入日志

	_, err := taos.ExecContext(ctx, sqlx)
	if err != nil {
		return err
	}

	return nil
}

func (t *AppLog) FindList(ctx context.Context, taos *sql.DB, tddb *TdDb, current, pageSize, startTime, endTime int64) (resp []*AppLog, err error) {

	// 拼接请求数据库和表
	sqlData := squirrel.Select("*").From(tddb.DbName).OrderBy("`ts` desc")
	// 拼接参数

	if current < 1 {
		current = 1
	}
	offset := (current - 1) * pageSize
	sqlData = t.BetweenTime(sqlData, startTime, endTime)

	sqlx, values, _ := t.fillFilter(sqlData).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()

	rows, err := taos.QueryContext(ctx, sqlx, values...)
	if err != nil {
		return nil, err
	}

	var datas []map[string]any
	err = Scan(rows, &datas)
	if err != nil {
		return nil, err
	}

	databytes, _ := jsonx.Marshal(datas)
	err = jsonx.Unmarshal(databytes, &resp)
	if err != nil {
		return nil, err
	}

	return resp, err
}

func (t *AppLog) Count(ctx context.Context, taos *sql.DB, tddb *TdDb, startTime, endTime int64) int64 {

	// 拼接请求数据库和表
	sqlData := squirrel.Select("COUNT('ts')").From(tddb.DbName)
	// 拼接参数

	sqlData = t.BetweenTime(sqlData, startTime, endTime)

	sqlx, values, _ := t.fillFilter(sqlData).ToSql()

	rows, err := taos.QueryContext(ctx, sqlx, values...)
	if err != nil {
		return 0
	}

	var count int64
	err = Scan(rows, &count)
	if err != nil {
		return 0
	}

	return count
}

func (t *AppLog) fillFilter(sql squirrel.SelectBuilder) squirrel.SelectBuilder {
	if len(t.CreatedName) != 0 {
		sql = sql.Where(fmt.Sprintf(" `created_name` like '%v' ", "%"+t.CreatedName+"%"))
	}

	sql = sql.Where(fmt.Sprintf(" `tenant_id` = '%v' ", 2))
	return sql
}

func (t *AppLog) BetweenTime(sql squirrel.SelectBuilder, startTime, endTime int64) squirrel.SelectBuilder {
	if startTime != 0 {
		sql = sql.Where(fmt.Sprintf(" ts >= '%v'", time.UnixMilli(startTime).Format(time.RFC3339Nano)))
	}

	if endTime != 0 {
		sql = sql.Where(fmt.Sprintf(" ts <= '%v'", time.UnixMilli(endTime).Format(time.RFC3339Nano)))
	}

	if startTime == 0 && endTime == 0 {
		sql = sql.Where(fmt.Sprintf(" ts >= '%v'", time.UnixMilli(time.Now().UnixMilli()-43200000).Format(time.RFC3339Nano)))
	}
	return sql
}

func createDataBase(taos *sql.DB) {

	// 创建对应的数据库  超级表
	//createLog(taos, "app_log", )

	a := fmt.Sprintf("CREATE STABLE app_log.tpmt_app_log (ts TIMESTAMP, created_name BINARY(36)" +
		") TAGS (tenant_id BINARY(36))")

	CreateStable(taos, a)
	time.Sleep(time.Second * 1)

	fmt.Println("初始化成功")

}

func createLog(taos *sql.DB, name string, number int) {
	dataString := fmt.Sprintf("CREATE DATABASE %s BUFFER 16 KEEP %vd  VGROUPS 2   ", name, number)
	_, err := taos.Exec(dataString)
	if err != nil {
		log.Fatalln("failed to create database, err:", err)
	}
}

func CreateStable(taos *sql.DB, dataString string) {
	_, err := taos.Exec(dataString)
	if err != nil {
		log.Fatalln("failed to create stable, err:", err)
	}
}

func Scan(rows *sql.Rows, Dest any) error {
	columns, _ := rows.Columns()
	values := make([]any, len(columns))

	switch dest := Dest.(type) {
	case map[string]any, *map[string]any:
		if rows.Next() {
			columnTypes, _ := rows.ColumnTypes()
			prepareValues(values, columnTypes, columns)
			if err := rows.Scan(values...); err != nil {
				return err
			}

			mapValue, ok := dest.(map[string]any)
			if !ok {
				if v, ok := dest.(*map[string]any); ok {
					mapValue = *v
				}
			}
			scanIntoMap(mapValue, values, columns)
		}
	case *[]map[string]any:
		columnTypes, _ := rows.ColumnTypes()
		for rows.Next() {
			prepareValues(values, columnTypes, columns)
			if err := rows.Scan(values...); err != nil {
				return err
			}

			mapValue := map[string]any{}
			scanIntoMap(mapValue, values, columns)
			*dest = append(*dest, mapValue)
		}
	case *int, *int8, *int16, *int32, *int64,
		*uint, *uint8, *uint16, *uint32, *uint64, *uintptr,
		*float32, *float64,
		*bool, *string, *time.Time,
		*sql.NullInt32, *sql.NullInt64, *sql.NullFloat64,
		*sql.NullBool, *sql.NullString, *sql.NullTime:
		for rows.Next() {
			if err := rows.Scan(dest); err != nil {
				return err
			}
		}
	default:
		return errors.New("not support type")
	}
	return nil
}

func scanIntoMap(mapValue map[string]any, values []any, columns []string) {
	for idx, column := range columns {
		if reflectValue := reflect.Indirect(reflect.Indirect(reflect.ValueOf(values[idx]))); reflectValue.IsValid() {
			mapValue[column] = reflectValue.Interface()
			if valuer, ok := mapValue[column].(driver.Valuer); ok {
				mapValue[column], _ = valuer.Value()
			} else if b, ok := mapValue[column].(sql.RawBytes); ok {
				mapValue[column] = string(b)
			}
		} else {
			mapValue[column] = nil
		}
	}
}

func prepareValues(values []any, columnTypes []*sql.ColumnType, columns []string) {
	if len(columnTypes) > 0 {
		for idx, columnType := range columnTypes {
			if columnType.ScanType() != nil {
				values[idx] = reflect.New(reflect.PtrTo(columnType.ScanType())).Interface()
			} else {
				values[idx] = new(any)
			}
		}
	} else {
		for idx := range columns {
			values[idx] = new(any)
		}
	}
}
